use serde::{Deserialize, Serialize};

const FINGERPRINT_PT:u64 = 0xbfe6b8a5bf378d83;
pub const RABIN_POLY_VALUE:u64 = FINGERPRINT_PT;
const BREAKMARK_VALUE:u64 = 0x78;
pub const POLY_XSHIFT:u32 = 64 - RABIN_POLY_VALUE.leading_zeros() - 1;
pub const POLY_SHIFT:u32 = POLY_XSHIFT - 8;
const ONE_RHL8_MOD_POLY:u64 = poly_mod(0, 1 << POLY_XSHIFT, RABIN_POLY_VALUE);
pub const RHL8_MOD_POLY:[u64;256] = seq!(N in 0..256 { [#(poly_mult_mod(N, ONE_RHL8_MOD_POLY, RABIN_POLY_VALUE) | (N << POLY_XSHIFT),)*] }); // T

pub const WINDOW_SIZE:usize = 48;
const SIZE_SHIFT:u64 = {
	let mut i = 0;
	let mut size_shift = 1;
	while i < WINDOW_SIZE {
		size_shift = (size_shift << 8) ^ RHL8_MOD_POLY[(size_shift >> POLY_SHIFT) as usize];
		i += 1;
	}
	size_shift
};

pub const KICK_MOD_POLY:[u64;256] = seq!(N in 0..256 { [#(poly_mult_mod(N, SIZE_SHIFT, RABIN_POLY_VALUE),)*] });

const fn poly_mod(high:u64, low:u64, poly:u64) -> u64 {
	let k = 63 - poly.leading_zeros();

	let poly = poly << poly.leading_zeros();
    let mut high = high;
    let mut low = low;

	if high != 0 {
        let mut i = 64;
		loop {
            i -= 1;
			if (high & (1 << i)) != 0 {
				high ^= poly >> (63 - i);
				low ^= poly << (i + 1);
			}
            if i == 0 { break; }
        }
	}
    let mut i = 64;
	loop {
        i -= 1;
		if (low & (1 << i)) != 0 {
			low ^= poly >> (63 - i);
        }
        if i == k { break; }
		// printf ("polymod : nl = %llun", nl);
	}

	// printf ("polymod : returning %llun", nl);

	return low;
}

// return (low, high)
const fn poly_multiply(a:u64, b:u64) -> (u64, u64) {
    let mut result_high = 0;
    let mut result_low = 0;
	if (a & 1) != 0 { result_low ^= b; }
    let mut i = 1;
    while i < 64 {
		if (a & (1 << i)) != 0
		{
			result_high ^= b >> (64 - i);
			result_low ^= b << i;
		}
        i += 1;
    }
    (result_high, result_low)
}

const fn poly_mult_mod(a:u64, b:u64, poly:u64) -> u64 {
    let (high, low) = poly_multiply(a, b);
    return poly_mod(high, low, poly);
}

#[inline(always)]
pub fn slide_one_byte(new_byte:u8, fp:u64, window:&mut [u8], pos:&mut usize) -> u64 {
	let mut fp = fp;
	if *pos + 1 >= WINDOW_SIZE {
		*pos = 0;
	} else {
		*pos += 1;
	}
	let kick_byte = window[*pos];
	window[*pos] = new_byte;          
	fp ^= KICK_MOD_POLY[kick_byte as usize];              
	let high_overflow = fp as usize >> POLY_SHIFT;          
	fp <<= 8;                 
	fp |= new_byte as u64;                  
	fp ^= RHL8_MOD_POLY[high_overflow];   
	fp
}

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct RabinBuilder {
    min_size: Option<usize>,
    avg_size: Option<usize>,
    max_size: Option<usize>,
}

impl RabinBuilder {
	pub fn new() -> Self {
		Self { min_size: None, avg_size: None, max_size: None }
	}

	pub fn min_size(&mut self, min_size:usize) -> &mut Self {
		self.min_size = Some(min_size);
		self
	}

	pub fn avg_size(&mut self, avg_size:usize) -> &mut Self {
		self.avg_size = Some(avg_size);
		self
	}

	pub fn max_size(&mut self, max_size:usize) -> &mut Self {
		self.max_size = Some(max_size);
		self
	}

	pub fn build(mut self) -> RabinConfig {
		let missed_parameter= 
			self.min_size.is_none() || 
			self.avg_size.is_none() || 
			self.max_size.is_none();
		let illegal = 
			self.min_size > self.avg_size || 
			self.avg_size > self.max_size;
		let invalid = missed_parameter || illegal;
		if invalid {
			self.min_size = None;
			self.avg_size = None;
			self.max_size = None;
		}
		let min_size = self.min_size.unwrap_or(2048);
		let avg_size = self.avg_size.unwrap_or(8192);
		let max_size = self.avg_size.unwrap_or(65536);
		return RabinConfig::new(min_size, avg_size, max_size);
	}
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub struct RabinConfig {
    min_size: usize,
    avg_size: usize,
    max_size: usize,
}

impl RabinConfig {
	pub fn new(min_size: usize,avg_size: usize,max_size: usize) -> Self {
		let avg_size = 1 << super::fatscdc::round2power(avg_size);
		assert!(min_size<=avg_size);
		assert!(avg_size<=max_size);
		Self {
			min_size, max_size, avg_size,
		}
	}

	pub fn min_chunking_len(&self) -> usize {
		self.max_size
	}

	pub fn chunking(&self, data:&[u8]) -> usize {
		let mut window_pos = 0;
		let mut fp = 0;
		let mut window = [0u8;WINDOW_SIZE];
		let avg_size = self.avg_size as u64;
		if data.len() <= self.min_size { return data.len(); }
		let upper_bound = std::cmp::min(self.max_size, data.len());
		for i in self.min_size..upper_bound {
			fp = slide_one_byte(data[i], fp, &mut window, &mut window_pos);
			if (fp & (avg_size - 1)) == BREAKMARK_VALUE { return i + 1; }
		}
		return upper_bound;
	}

	pub fn normalized_chunking(&self, data:&[u8]) -> usize {
		let mut window_pos = 0;
		let mut fp = 0;
		let mut window = [0u8;64];
		if data.len() <= self.min_size { return data.len(); }
		let mid_bound = std::cmp::min(self.avg_size, data.len());
		let upper_bound = std::cmp::min(self.max_size, data.len());
		let avg_size = self.avg_size as u64;
		for i in self.min_size..mid_bound {
			fp = slide_one_byte(data[i], fp, &mut window, &mut window_pos);
			if (fp & (avg_size * 2 - 1)) == BREAKMARK_VALUE { return i + 1; }
		}
		for i in mid_bound..upper_bound {
			fp = slide_one_byte(data[i], fp, &mut window, &mut window_pos);
			if (fp & (avg_size / 2 - 1)) == BREAKMARK_VALUE { return i + 1; }
		}
		return upper_bound;
	}
}

impl Default for RabinConfig {
	fn default() -> Self {
		Self { min_size: 2048, avg_size: 8192, max_size: 65536 }
	}
}

#[test]
fn test_chunking() {
	let test_len = 0x10000000;
	let mut data = vec![0u8;test_len];
	for i in 0..test_len {
		data[i] = ((((i as u64 * 0x81DAC87) % 0x822267) % 0x9383) & 0xff) as u8;
	}
	let now = std::time::Instant::now();
	let rabin_config = RabinConfig::new(4096, 8192, 65536);
	let mut slice = &data as &[u8];
	while slice.len() != 0 {
		let off = rabin_config.chunking(slice);
		slice = &slice[off..];
	}
	let time = now.elapsed().as_micros();
	println!("rabin func chunking:{}", time);
}